<?php
class Flexihash
{
	private  $_replicas               = 128;
	private  $_targetCount            = 0;
	public   $_positionToTarget       = array();
	public   $_targetToPositions      = array();
	private  $_positionToTargetSorted = false;
	private  static  $_my;
	private function __construct(){}
	public static function intance(){
		if(self::$_my===null){
			self::$_my=new self();
		}
		return self::$_my;
	}	
	public function addTarget($target, $weight=1){
		if (isset($this->_targetToPositions[$target])){
			throw new Exception("Target '$target' already exists.");
		}
		$this->_targetToPositions[$target] = array();
		for($i=0;$i<round($this->_replicas*$weight);$i++){
			$position = $this->hash($target.$i);
			$this->_positionToTarget[$position] = $target;
			$this->_targetToPositions[$target] []= $position;
		}
		$this->_positionToTargetSorted = false;
		$this->_targetCount++;
		return $this;
	}	
	public function addTargets($targets, $weight=1){
		foreach ($targets as $target){
			$this->addTarget($target,$weight);
		}
		return $this;
	}
	public function removeTarget($target){
		if (!isset($this->_targetToPositions[$target])){
			throw new Exception("Target '$target' does not exist.");
		}
		foreach ($this->_targetToPositions[$target] as $position){
			unset($this->_positionToTarget[$position]);
		}
		unset($this->_targetToPositions[$target]);
		$this->_targetCount--;
		return $this;
	}
	public function getAllTargets(){
		return array_keys($this->_targetToPositions);
	}
	public function lookup($resource){
		$targets = $this->lookupList($resource, 1);
		if (empty($targets)) throw new Exception('No targets exist');
		return $targets[0];
	}
	public function lookupList($resource, $requestedCount=1){
		if (!$requestedCount) throw new Exception('Invalid count requested');
		if (empty($this->_positionToTarget)) return array();
		if ($this->_targetCount == 1) return array_unique(array_values($this->_positionToTarget));
		$resourcePosition = $this->hash($resource);
		$results = array();
		$collect = false;
		$this->_sortPositionTargets();
		foreach ($this->_positionToTarget as $key => $value){
			if (!$collect && $key > $resourcePosition){
				$collect = true;
			}
			if ($collect && !in_array($value, $results)){
				$results []= $value;
			}
			if (count($results) == $requestedCount || count($results) == $this->_targetCount){
				return $results;
			}
		}
		foreach ($this->_positionToTarget as $key => $value){
			if (!in_array($value, $results)){
				$results []= $value;
			}
			if (count($results) == $requestedCount || count($results) == $this->_targetCount){ 
				return $results;
			}
		}
		return $results;
	}
	private function _sortPositionTargets(){
		if (!$this->_positionToTargetSorted){
			ksort($this->_positionToTarget, SORT_REGULAR);
			$this->_positionToTargetSorted = true;
		}
	}
	private function hash($string){
		return substr(md5($string),0,8);
   //	return crc32($string);
	}
}